Skip to content

feat: Screen Wake Lock API on Page#24324

Open
Artur- wants to merge 6 commits into
mainfrom
feature/wake-lock
Open

feat: Screen Wake Lock API on Page#24324
Artur- wants to merge 6 commits into
mainfrom
feature/wake-lock

Conversation

@Artur-
Copy link
Copy Markdown
Member

@Artur- Artur- commented May 12, 2026

Add a per-UI WakeLock facade reached through Page#getWakeLock(). It exposes request(), release(), and an active-state Signal, matching the conventions used for PageVisibility and Geolocation:

  • Dedicated facade class under com.vaadin.flow.component.page with a package-private constructor.
  • Client logic lives in flow-client/src/main/frontend/WakeLock.ts and is side-effect-imported from Flow.ts; nothing inlined into Java.
  • State travels back as a vaadin-wake-lock-change CustomEvent on document.body with an ACTIVE / RELEASED detail; the facade listens on the UI element and writes to a private ValueSignal.
  • Client transparently re-acquires the lock on visibilitychange so a single request() covers the lifetime of a view; release() clears the "want lock" flag and drops the current sentinel.
  • Failures (insecure context, unsupported browser, denied) log at DEBUG and leave the active signal in its false state.

Add a per-UI WakeLock facade reached through Page#getWakeLock(). It
exposes request(), release(), and an active-state Signal<Boolean>,
matching the conventions used for PageVisibility and Geolocation:

- Dedicated facade class under com.vaadin.flow.component.page with a
  package-private constructor.
- Client logic lives in flow-client/src/main/frontend/WakeLock.ts and
  is side-effect-imported from Flow.ts; nothing inlined into Java.
- State travels back as a vaadin-wake-lock-change CustomEvent on
  document.body with an ACTIVE / RELEASED detail; the facade listens
  on the UI element and writes to a private ValueSignal.
- Client transparently re-acquires the lock on visibilitychange so a
  single request() covers the lifetime of a view; release() clears
  the "want lock" flag and drops the current sentinel.
- Failures (insecure context, unsupported browser, denied) log at
  DEBUG and leave the active signal in its false state.
Signal.effect(Component, EffectAction) has no (Object, Runnable)
overload, so the original @link couldn't resolve and the strict
javadoc check in CI failed. Switch to the same {@code Signal.effect(...)}
shape used by PageVisibility and Geolocation.
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 12, 2026

Test Results

 1 407 files  +1   1 407 suites  +1   1h 22m 0s ⏱️ -29s
10 146 tests +7  10 076 ✅ +7  70 💤 ±0  0 ❌ ±0 
10 621 runs  +7  10 542 ✅ +7  79 💤 ±0  0 ❌ ±0 

Results for commit 90006de. ± Comparison against base commit 32183d1.

♻️ This comment has been updated with latest results.

*
* @return the wake lock facade, never {@code null}
*/
public WakeLock getWakeLock() {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Other browser feature APIs have their own static methods instead of being accessed throug Page.
Should we make this consistent?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WakeLock is the odd one out — it uses the older per-UI facade pattern (Page.getWakeLock()), modeled on Page/History.

What is already aligned

  • vaadin-wake-lock-change DOM event prefix ✓
  • Event-to-signal bridging through ui.getElement() listener ✓
  • window.Vaadin.Flow.wakeLock.* namespace ✓
  • WakeLock.ts imported from Flow.ts ✓
  • Cached read-only signal wrapper ✓
  • DEBUG-level executeJs error logging ✓
  • allowInert() on the state-change listener ✓

Where it diverges

Aspect: Entry point
Geolocation / Clipboard: Geolocation.getPosition(...) static
WakeLock: page.getWakeLock().request() facade
Recommendation: Redesign as static: WakeLock.request() / WakeLock.release() / WakeLock.activeSignal(), each with a (UI ui) overload; store active state on
UIInternals; drop Page.getWakeLock().
────────────────────────────────────────
Aspect: Package
Geolocation / Clipboard: com.vaadin.flow.component.geolocation (own, @NullMarked)
WakeLock: com.vaadin.flow.component.page (legacy, not @NullMarked)
Recommendation: Move to com.vaadin.flow.component.wakelock with @NullMarked package-info.java.
────────────────────────────────────────
Aspect: Capability hint
Geolocation / Clipboard: availabilityHintSignal() — GeolocationAvailability enum, seeded via bootstrap v-ga param
WakeLock: None — activeSignal() silently stays false when unsupported
Recommendation: Add WakeLock.availabilitySignal() returning Signal (SUPPORTED / UNSUPPORTED / UNKNOWN); probe 'wakeLock' in navigator &&
window.isSecureContext in collectBrowserDetails and seed via a v-wla bootstrap param.
────────────────────────────────────────
Aspect: Test seam
Geolocation / Clipboard: GeolocationClient + GeolocationClientFactory Lookup SPI
WakeLock: None — WakeLockSignalTest only verifies the JS-invocation string
Recommendation: Optional. Useful for browserless ITs, but heavier-weight for binary on/off state. Skip unless we plan to test scenarios beyond the current
signal-wiring tests.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you want me to push an update for this or will you do it?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please go ahead if you have time

* {@link #activeSignal()} to react to the actual state. Calling
* {@code request()} when a lock is already held is a no-op.
*/
public void request() {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if there should be an override taking an error handler function as an argument.
Let's assume there's a "Lock" button on the page that calls this API; you click on it, but the lock request fails; however, there will be no feedback to the user. In the worst case, you might also have disabled some other components, and there's no way to recover other than refreshing the whole page.

Artur- added 4 commits May 28, 2026 13:59
Moves WakeLock from com.vaadin.flow.component.page to its own
@NullMarked package and replaces the per-UI facade reached through
Page.getWakeLock() with static request()/release()/activeSignal()
methods, each with an explicit-UI overload for background-thread
callers. Per-UI state lives on UIInternals; the DOM listener that
bridges vaadin-wake-lock-change events is installed lazily on first
use. Drops Page.getWakeLock().

Adds WakeLock.availabilitySignal() returning a Signal<WakeLockAvailability>
(SUPPORTED / UNSUPPORTED / UNKNOWN). The client probes
'wakeLock' in navigator && window.isSecureContext during
collectBrowserDetails and ships the result as v-wla;
ExtendedClientDetails seeds the signal from the bootstrap payload.

This brings the API shape in line with Clipboard and Geolocation, both
of which are static utilities with own @NullMarked packages and
bootstrap-seeded availability hints.
Adds WakeLock.request(SerializableConsumer<WakeLockError>) and a
matching (consumer, UI) overload, so a "Keep screen on" button can
surface a message and re-enable itself when the browser refuses the
request instead of silently leaving the UI in a half-disabled state.

The client return type is now a structured result discriminating
'granted' / 'deferred' (page hidden, will retry on visibilitychange)
/ 'error' (persistent failure). The server fail-fasts on UNSUPPORTED
availability — when the bootstrap probe already established the API
is unusable, the error consumer fires synchronously without paying a
round-trip. NotAllowedError maps to NOT_ALLOWED; everything else
folds into UNKNOWN.

The no-handler request() overloads keep their existing fire-and-forget
behavior — failures still log at DEBUG only.

Addresses PR #24324 review comment from mcollovati.
@sonarqubecloud
Copy link
Copy Markdown

});
}

private static void handleResult(UI ui,
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had a hard time realizing that this method is basically handling only the potential error and not missing some implementation 😄
Could we rename the method to something like handleResultError?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

Status: 🔎Iteration reviews

Development

Successfully merging this pull request may close these issues.

3 participants